package com.gitee.wsl.coroutines.job.trigger

import com.gitee.wsl.coroutines.job.JobDataMap
import com.gitee.wsl.coroutines.job.JobDetail
import com.gitee.wsl.coroutines.job.JobKey
import com.gitee.wsl.ext.base.currentTime
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

@OptIn(ExperimentalTime::class)
class TriggerBuilder<T : ITrigger>
private constructor() {
    private var m_aKey: TriggerKey? = null
    private var m_sDescription: String? = null
    private var m_aStartTime: Instant = currentTime
    private var m_aEndTime: Instant? = null
    private var m_nPriority = ITrigger.DEFAULT_PRIORITY
    private var m_sCalendarName: String? = null
    private var m_aJobKey: JobKey? = null
    private var m_aJobDataMap = JobDataMap()
    private var m_aScheduleBuilder: IScheduleBuilder<out IMutableTrigger>? = null

    /**
     * Produce the `Trigger`.
     *
     * @return a Trigger that meets the specifications of the builder.
     */
    
    fun build(): T {
        if (m_aScheduleBuilder == null)
            m_aScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
        val trig: IMutableTrigger = m_aScheduleBuilder!!.build()
        m_sCalendarName?.let { trig.calendarName=it }
        m_sDescription?.let { trig.description=it }
        trig.startTime=(m_aStartTime)
        m_aEndTime?.let { trig.endTime=it }
        if (m_aKey == null)
            m_aKey = TriggerKey()
        trig.key=m_aKey!!
        if (m_aJobKey != null)
            trig.jobKey=m_aJobKey!!
        trig.priority=m_nPriority

        if (!m_aJobDataMap.isEmpty())
            trig.jobDataMap=m_aJobDataMap

        return trig as T
    }

    /**
     * Use a `TriggerKey` with the given name and default group to
     * identify the Trigger.
     *
     *
     * If none of the 'withIdentity' methods are set on the TriggerBuilder, then a
     * random, unique TriggerKey will be generated.
     *
     *
     * @param name
     * the name element for the Trigger's TriggerKey
     * @return the updated TriggerBuilder
     * @see TriggerKey
     *
     * @see ITrigger.getKey
     */
    
    fun withIdentity(name: String): TriggerBuilder<T> {
        m_aKey = TriggerKey(name, null)
        return this
    }

    /**
     * Use a TriggerKey with the given name and group to identify the Trigger.
     *
     *
     * If none of the 'withIdentity' methods are set on the TriggerBuilder, then a
     * random, unique TriggerKey will be generated.
     *
     *
     * @param name
     * the name element for the Trigger's TriggerKey
     * @param group
     * the group element for the Trigger's TriggerKey
     * @return the updated TriggerBuilder
     * @see TriggerKey
     *
     * @see ITrigger.getKey
     */
    
    fun withIdentity(name: String, group: String): TriggerBuilder<T> {
        m_aKey = TriggerKey(name, group)
        return this
    }

    /**
     * Use the given TriggerKey to identify the Trigger.
     *
     *
     * If none of the 'withIdentity' methods are set on the TriggerBuilder, then a
     * random, unique TriggerKey will be generated.
     *
     *
     * @param triggerKey
     * the TriggerKey for the Trigger to be built
     * @return the updated TriggerBuilder
     * @see TriggerKey
     *
     * @see ITrigger.getKey
     */
    
    fun withIdentity(triggerKey: TriggerKey?): TriggerBuilder<T> {
        m_aKey = triggerKey
        return this
    }

    /**
     * Set the given (human-meaningful) description of the Trigger.
     *
     * @param triggerDescription
     * the description for the Trigger
     * @return the updated TriggerBuilder
     * @see ITrigger.getDescription
     */
    
    fun withDescription(triggerDescription: String): TriggerBuilder<T> {
        m_sDescription = triggerDescription
        return this
    }

    /**
     * Set the Trigger's priority. When more than one Trigger have the same fire
     * time, the scheduler will fire the one with the highest priority first.
     *
     * @param triggerPriority
     * the priority for the Trigger
     * @return the updated TriggerBuilder
     * @see ITrigger.DEFAULT_PRIORITY
     *
     * @see ITrigger.getPriority
     */
    
    fun withPriority(triggerPriority: Int): TriggerBuilder<T> {
        m_nPriority = triggerPriority
        return this
    }

    /**
     * Set the name of the [ICalendar] that should be applied to this
     * Trigger's schedule.
     *
     * @param calName
     * the name of the Calendar to reference.
     * @return the updated TriggerBuilder
     * @see ICalendar
     *
     * @see ITrigger.getCalendarName
     */
    
    fun modifiedByCalendar(calName: String): TriggerBuilder<T> {
        m_sCalendarName = calName
        return this
    }

    /**
     * Set the time the Trigger should start at - the trigger may or may not fire
     * at this time - depending upon the schedule configured for the Trigger.
     * However the Trigger will NOT fire before this time, regardless of the
     * Trigger's schedule.
     *
     * @param triggerStartTime
     * the start time for the Trigger.
     * @return the updated TriggerBuilder
     * @see ITrigger.getStartTime
     * @see DateBuilder
     */
    
    fun startAt(triggerStartTime: Instant?): TriggerBuilder<T> {
        m_aStartTime = triggerStartTime?: currentTime
        return this
    }

    /**
     * Set the time the Trigger should start at to the current moment - the
     * trigger may or may not fire at this time - depending upon the schedule
     * configured for the Trigger.
     *
     * @return the updated TriggerBuilder
     * @see ITrigger.getStartTime
     */
    
    fun startNow(): TriggerBuilder<T> {
        return startAt(currentTime)
    }

    /**
     * Set the time at which the Trigger will no longer fire - even if it's
     * schedule has remaining repeats.
     *
     * @param triggerEndTime
     * the end time for the Trigger. If null, the end time is indefinite.
     * @return the updated TriggerBuilder
     * @see ITrigger.getEndTime
     * @see DateBuilder
     */
    
    fun endAt(triggerEndTime: Instant?): TriggerBuilder<T> {
        m_aEndTime = triggerEndTime
        return this
    }

    /**
     * Set the [IScheduleBuilder] that will be used to define the Trigger's
     * schedule.
     *
     *
     * The particular `SchedulerBuilder` used will dictate the concrete
     * type of Trigger that is produced by the TriggerBuilder.
     *
     *
     * @param schedBuilder
     * the SchedulerBuilder to use.
     * @return the updated TriggerBuilder
     * @see IScheduleBuilder
     *
     * @see SimpleScheduleBuilder
     *
     * @see CronScheduleBuilder
     *
     * @see CalendarIntervalScheduleBuilder
     *
     * @param <SBT>
     * Type to use
    </SBT> */
    
    fun <SBT : IMutableTrigger> withSchedule(schedBuilder: IScheduleBuilder<SBT>): TriggerBuilder<SBT> {
        m_aScheduleBuilder = schedBuilder
        return this as TriggerBuilder<SBT>
    }

    /**
     * Set the identity of the Job which should be fired by the produced Trigger.
     *
     * @param keyOfJobToFire
     * the identity of the Job to fire.
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobKey
     */
    
    fun forJob(keyOfJobToFire: JobKey?): TriggerBuilder<T> {
        m_aJobKey = keyOfJobToFire
        return this
    }

    /**
     * Set the identity of the Job which should be fired by the produced Trigger -
     * a `JobKey` will be produced with the given name and default
     * group.
     *
     * @param jobName
     * the name of the job (in default group) to fire.
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobKey
     */
    
    fun forJob(jobName: String): TriggerBuilder<T> {
        m_aJobKey = JobKey(jobName, null)
        return this
    }

    /**
     * Set the identity of the Job which should be fired by the produced Trigger -
     * a `JobKey` will be produced with the given name and group.
     *
     * @param jobName
     * the name of the job to fire.
     * @param jobGroup
     * the group of the job to fire.
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobKey
     */
    
    fun forJob(jobName: String, jobGroup: String): TriggerBuilder<T> {
        m_aJobKey = JobKey(jobName, jobGroup)
        return this
    }

    /**
     * Set the identity of the Job which should be fired by the produced Trigger,
     * by extracting the JobKey from the given job.
     *
     * @param jobDetail
     * the Job to fire.
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobKey
     */
    
    fun forJob( jobDetail: JobDetail): TriggerBuilder<T> {
        val k = jobDetail.key
        if (k?.name == null) throw IllegalArgumentException("The given job has not yet had a name assigned to it.")
        m_aJobKey = k
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: String): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: Int): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: Long): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: Float): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: Double): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Add the given key-value pair to the Trigger's [JobDataMap].
     *
     * @param dataKey
     * data key
     * @param value
     * value
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(dataKey: String, value: Boolean): TriggerBuilder<T> {
        m_aJobDataMap[dataKey] = value
        return this
    }

    /**
     * Set the Trigger's [JobDataMap], adding any values to it that were
     * already set on this TriggerBuilder using any of the other 'usingJobData'
     * methods.
     *
     * @param newJobDataMap
     * New job data map
     * @return the updated TriggerBuilder
     * @see ITrigger.getJobDataMap
     */
    
    fun usingJobData(newJobDataMap: JobDataMap): TriggerBuilder<T> {
        // add any existing data to this new map
        newJobDataMap.putAll(m_aJobDataMap)
        m_aJobDataMap = newJobDataMap // set new map as the map to use
        return this
    }

    companion object {
        /**
         * Create a new TriggerBuilder with which to define a specification for a
         * Trigger.
         *
         * @return the new TriggerBuilder
         */
        
        fun<I:ITrigger> newTrigger(): TriggerBuilder<I> {
            return TriggerBuilder()
        }


    }
}
